@file:OptIn(ExperimentalComposeUiApi::class)

package com.fanketly.musicdiary.test

import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.layout.*
import androidx.compose.ui.modifier.ModifierLocalNode
import androidx.compose.ui.modifier.modifierLocalMapOf
import androidx.compose.ui.modifier.modifierLocalOf
import androidx.compose.ui.node.*
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.platform.inspectable
import androidx.compose.ui.semantics.SemanticsConfiguration
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import com.fanketly.musicdiary.data.TAG
import com.fanketly.musicdiary.ui.theme.Pink
import com.fanketly.musicdiary.ui.theme.TestTheme

/**
 *Version 1.3.0-beta01 重要更新
 **/
@Preview(showBackground = true)
@Composable
fun NodePreview() {
    TestTheme {
        Column {
//            TestCircle()
//            TestTap()
//            TestOnPointerEvent()
//            TestHeading()
//            TestLogSize()
//            TestLogPosition()
//            TestLog2()
            LayoutModifierNodeSample()
        }
    }
}

///////////////////////////////////////////////////////////////////////////
//新版本 Node
///////////////////////////////////////////////////////////////////////////

@Composable
fun LayoutModifierNodeSample() {
    class VerticalPadding(var padding: Dp) : LayoutModifierNode, Modifier.Node() {
        override fun MeasureScope.measure(
            measurable: Measurable,
            constraints: Constraints
        ): MeasureResult {
            val paddingPx = padding.roundToPx()
            val placeable = measurable.measure(constraints.offset(vertical = -paddingPx))
            return layout(placeable.width, placeable.height + paddingPx) {
                placeable.placeRelative(0, paddingPx)
            }
        }
    }

    fun Modifier.verticalPadding(padding: Dp) = this then modifierElementOf(
        params = padding,
        create = { VerticalPadding(padding) },
        update = { it.padding = padding },
        definitions = {
            name = "verticalPadding"
            properties["padding"] = padding
        }
    )

    Box(
        Modifier
            .background(Color.Gray)
            .verticalPadding(50.dp)
    ) {
        Box(
            Modifier
                .fillMaxSize()
                .background(Color.DarkGray)
        )
    }
}

/**
 *SemanticsModifierNodeSample
 **/
@Composable
private fun TestHeading() {
    Box(
        modifier = Modifier
            .size(100.dp)
            .circle(Pink)
            .heading()
    )
}

/**
 *SemanticsModifierNode一个Modifier.Node添加语义键/值以用于测试、可访问性和类似用例。
 *SemanticsConfiguration 描述与所属组件相关的语义信息,配置中提供的信息用于生成语义树
 **/
@OptIn(ExperimentalComposeUiApi::class)
class HeadingNode : SemanticsModifierNode, Modifier.Node() {
    override val semanticsConfiguration: SemanticsConfiguration = SemanticsConfiguration()
        .apply {
            //该节点被标记为可访问性标题。
            heading()
        }
}

@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.heading() = this then modifierElementOf(
    create = { HeadingNode() },
    definitions = {
        name = "heading"
    }
)

/**
 *GlobalPositionAwareModifierNodeSample
 **/
@Composable
private fun TestLogPosition() {
    Box(
        modifier = Modifier
            .size(100.dp)
            .circle(Color.Yellow)
            .logPosition("A")
    )
}

/**
 * GlobalPositionAwareModifierNode一个androidx.compose.ui.Modifier.Node ，当内容的全局位置可能已经改变时，
 * 它的onGloballyPositioned会使用 Layout 的最终 LayoutCoordinates 调用。请注意，当坐标最终确定时，它将在合成后调用。
 **/
class PositionLoggerNode(var id: String) : GlobalPositionAwareModifierNode, Modifier.Node() {
    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
        // This will be the size of the Layout.
        coordinates.size
        // The position of the Layout relative to the application window.
        coordinates.positionInWindow()
        // The position of the Layout relative to the Compose root.
        coordinates.positionInRoot()
        // These will be the alignment lines provided to the Layout
        coordinates.providedAlignmentLines
        // This will be a LayoutCoordinates instance corresponding to the parent of the Layout.
        coordinates.parentLayoutCoordinates
    }
}

fun Modifier.logPosition(id: String) = this then modifierElementOf(
    params = id,
    create = { PositionLoggerNode(id) },
    update = { it.id = id },
    definitions = {
        name = "logPosition"
        properties["id"] = id
    }
)

/**
 *LayoutAwareModifierNodeSample
 **/
@Composable
private fun TestLogSize() {
    val size = remember {
        mutableStateOf(50.dp)
    }
    Box(
        modifier = Modifier
            .size(size.value)
            .circle(Color.Yellow)
            .logSize("A")
            .clickable {
                size.value += 10.dp
            }
    )
}

/**
 *LayoutAwareModifierNode一个androidx.compose.ui.Modifier.Node接收各种回调以响应布局中的本地更改。
 **/
class SizeLoggerNode(var id: String) : LayoutAwareModifierNode, Modifier.Node() {
    override fun onRemeasured(size: IntSize) {
        println("The size of $id was $size")
    }
}

fun Modifier.logSize(id: String) = this then modifierElementOf(
    params = id,
    create = { SizeLoggerNode(id) },
    update = { it.id = id },
    definitions = {
        name = "logSize"
        properties["id"] = id
    }
)

/**
 *ModifierElementOfSample
 **/
@Composable
private fun TestCircle() {
    Box(
        modifier = Modifier
            .size(100.dp)
            .circle(Color.Red)
    )
}


private class Circle(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        drawCircle(color)
    }
}


fun Modifier.circle(color: Color) = this then modifierElementOf(
    params = { color },
    create = {
        Circle(color)
    }, update = {
        it.color = color
    }, definitions = {
        name = "circle"
        properties["color"] = color
    })

/**
 *PointerInputModifierNodeSample
 **/
@Composable
private fun TestOnPointerEvent() {
    Box(
        modifier = Modifier
            .size(100.dp)
            .circle(Color.Red)
            .onPointerEvent {
                Log.i(TAG, "TestOnPointerEvent: ${it.type}")
            }
    )
}

class OnPointerEventNode(var callback: (PointerEvent) -> Unit) :
    PointerInputModifierNode, Modifier.Node() {
    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        if (pass == PointerEventPass.Initial) {
            callback(pointerEvent)
        }
    }

    override fun onCancelPointerInput() {
        // Do nothing
    }
}

fun Modifier.onPointerEvent(callback: (PointerEvent) -> Unit) = this then modifierElementOf(
    params = callback,
    create = { OnPointerEventNode(callback) },
    update = { it.callback = callback },
    definitions = {
        name = "onPointerEvent"
        properties["callback"] = callback
    }
)

/**
 *DelegatedNodeSample
 **/
@Composable
private fun TestTap() {
    Box(
        modifier = Modifier
            .size(100.dp)
            .circle(Color.Yellow)
            .tap {
                Log.i(TAG, "TestTap: ")
            }

    )
}

fun Modifier.tap(onTap: () -> Unit) = this then modifierElementOf(
    params = { onTap },
    create = {
        TapGestureWithClickSemantics(onTap)
    }, update = {
        it.onTap = onTap
    }, definitions = {
        name = "tap"
        properties["onTap"] = onTap
    })

private class TapGestureNode(var onTap: () -> Unit) : PointerInputModifierNode, Modifier.Node() {
    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        Log.i(TAG, "onPointerEvent: ${bounds.width},${bounds.height}")
    }

    override fun onCancelPointerInput() {
        Log.i(TAG, "onCancelPointerInput: ")
    }
}

/**
 *DelegatingNode一个Modifier.Node能够将工作委托给其他Modifier.Node实例。这对于将多个节点实现组合成一个很有用
 **/
@OptIn(ExperimentalComposeUiApi::class)
private class TapGestureWithClickSemantics(onTap: () -> Unit) :
    PointerInputModifierNode, SemanticsModifierNode, DelegatingNode() {
    var onTap: () -> Unit
        get() = gesture.onTap
        set(value) {
            gesture.onTap = value
        }

    val gesture = delegated { TapGestureNode(onTap) }

    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        gesture.onPointerEvent(pointerEvent, pass, bounds)
    }

    override fun onCancelPointerInput() {
        gesture.onCancelPointerInput()
    }

    override val semanticsConfiguration: SemanticsConfiguration = SemanticsConfiguration()
        .apply {
            onClick {
                gesture.onTap()
                true
            }
        }
}

/**
 *JustReadingOrProvidingModifierLocalNodeSample
 **/
@Composable
private fun TestLog2() {
    val size = remember {
        mutableStateOf(50.dp)
    }
    Box(
        modifier = Modifier
            .size(size.value)
            .circle(Color.Yellow)
            .provideLogger(logger = Logger())
            .logSize2("B")
//            .logSize("A")
            .clickable {
                size.value += 10.dp
            }

    )
}

class Logger {
    fun log(string: String) {
        println(string)
    }
}

/**
 *在没有提供者的情况下使用ModifierLocal的情况下创建默认值的工厂。如果这是你不想处理的情况，你可以在这个工厂中抛出一个错误。
以下是修饰符可以与同一修饰符链中的另一个进行通信的示例：
 **/
val loggerLocal = modifierLocalOf<Logger?> { null }

class ProvideLoggerNode(logger: Logger) : ModifierLocalNode, Modifier.Node() {
    /**
     *此节点提供的 ModifierLocal <-> 值对的映射。如果要提供任何值，则必须覆盖此值。它应该被重写为一个字段支持的属性，
     * 并使用它可能提供的所有键的值进行初始化。
    默认情况下，该属性将设置为空映射，这意味着该节点将仅消耗ModifierLocal并且不会提供任何新值。
    如果您想随时间更改地图中提供的值，则必须使用provide API。
     **/
    override val providedValues = modifierLocalMapOf(loggerLocal to logger)

}

class SizeLoggerNode2(var id: String) :
    ModifierLocalNode, LayoutAwareModifierNode, Modifier.Node() {

    override fun onRemeasured(size: IntSize) {
        Log.i(TAG, "onRemeasured: ${loggerLocal.current}")
        loggerLocal.current?.log("The size of $id was $size")
    }
}

fun Modifier.logSize2(id: String) = this then modifierElementOf(
    params = id,
    create = { SizeLoggerNode2(id) },
    update = {
        it.id = id
        Log.i(TAG, "logSize2:update ")
    },
    definitions = {
        name = "logSize"
        properties["id"] = id
    }
)

fun Modifier.provideLogger(logger: Logger) = this then modifierElementOf(
    params = logger,
    create = {
        Log.i(TAG, "provideLogger: $logger")
        ProvideLoggerNode(logger)
    },
    //当修改器应用于其params已从前一个应用程序更改的布局时调用。此 lambda 会将当前节点实例作为参数传入，并且预计该节点将被更新。
    update = { it.provide(loggerLocal, logger) },
    definitions = {
        name = "provideLogger"
        properties["logger"] = logger
    }
)

///////////////////////////////////////////////////////////////////////////
//old
///////////////////////////////////////////////////////////////////////////
@Composable
fun New() {
//    Text("A", modifier = Modifier.composed { })
    //composed的更高性能替代品
//    Modifier.Node

    val modifier = Modifier
    println(modifier.hashCode())
    val m = modifier.simpleFrame(Color.Red)
    println(m.hashCode())
    Text("A", modifier = Modifier.myModifier(500.dp, 500.dp, Color.Red))
//    C()
}

/**
 * 具有单个参数的样本
 */
fun Modifier.simpleFrame(color: Color) = inspectable(
    inspectorInfo = debugInspectorInfo {
        name = "simpleFrame"
        value = color
    }
) {
    background(color, RoundedCornerShape(5.0.dp))
}

/**
 * 具有多个参数的示例
 */
fun Modifier.fancyFrame(size: Dp, color: Color) = inspectable(
    inspectorInfo = debugInspectorInfo {
        name = "fancyFrame"
        properties["size"] = size
        properties["color"] = color
    }
) {
    background(color, RoundedCornerShape(size))
}

fun Modifier.myModifier(width: Dp, height: Dp, color: Color) = composed(
    // pass inspector information for debug
    inspectorInfo = debugInspectorInfo {
        // name should match the name of the modifier
        name = "myModifier"
        // add name and value of each argument
        properties["width"] = width
        properties["height"] = height
        properties["color"] = color
    },
    // pass your modifier implementation that resolved per modified element
    factory = {
        // add your modifier implementation here
        Modifier
            .width(width)
            .height(height)
    }
)